/*
 * JBoss, Home of Professional Open Source
 * Copyright XXXX, Red Hat Middleware LLC, and individual contributors as indicated
 * by the @authors tag. All rights reserved.
 * See the copyright.txt in the distribution for a full listing
 * of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU General Public License, v. 2.0.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License,
 * v. 2.0 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */
package org.example.server;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.Set;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.jdiameter.api.Answer;
import org.jdiameter.api.ApplicationId;
import org.jdiameter.api.Avp;
import org.jdiameter.api.AvpDataException;
import org.jdiameter.api.AvpSet;
import org.jdiameter.api.Configuration;
import org.jdiameter.api.InternalException;
import org.jdiameter.api.Message;
import org.jdiameter.api.MetaData;
import org.jdiameter.api.Network;
import org.jdiameter.api.NetworkReqListener;
import org.jdiameter.api.Request;
import org.jdiameter.api.Session;
import org.jdiameter.api.SessionFactory;
import org.jdiameter.api.Stack;
import org.jdiameter.api.StackType;
import org.jdiameter.server.impl.StackImpl;
import org.jdiameter.server.impl.helpers.XMLConfiguration;
import org.mobicents.diameter.dictionary.AvpDictionary;
import org.mobicents.diameter.dictionary.AvpRepresentation;

/**
 * @author baranowb
 * 
 */
public class ExampleServer implements NetworkReqListener {
	private static final Logger log = Logger.getLogger(ExampleServer.class);
	static{

		configLog4j();
	
}

private static void configLog4j() {
	InputStream inStreamLog4j = ExampleServer.class.getClassLoader().getResourceAsStream("log4j.properties");
	Properties propertiesLog4j = new Properties();
	try {
		propertiesLog4j.load(inStreamLog4j);
		PropertyConfigurator.configure(propertiesLog4j);
	} catch (Exception e) {
		e.printStackTrace();
	}

	log.debug("log4j configured");

}
	private static final String configFile = "org/example/server/server-jdiameter-config.xml";
	private static final String dictionaryFile = "org/example/client/dictionary.xml";
	private static final String realmName = "exchange.example.org";
	// Defs for our app
	private static final int commandCode = 686;
	private static final long vendorID = 66666;
	private static final long applicationID = 33333;
	private ApplicationId authAppId = ApplicationId.createByAuthAppId(applicationID);;
	private static final int exchangeTypeCode = 888;
	private static final int exchangeDataCode = 999;
	// enum values for Exchange-Type AVP
	private static final int EXCHANGE_TYPE_INITIAL = 0;
	private static final int EXCHANGE_TYPE_INTERMEDIATE = 1;
	private static final int EXCHANGE_TYPE_TERMINATING = 2;
	
	private static final String[] TO_RECEIVE = new String[] { "I want to get 3 answers", "This is second message", "Bye bye" };
	private AvpDictionary dictionary = AvpDictionary.INSTANCE;
	private Stack stack;
	private SessionFactory factory;

	// ////////////////////////////////////////
	// Objects which will be used in action //
	// ////////////////////////////////////////
	private Session session;
	private int toReceiveIndex = 0;
	private boolean finished = false;

	private void initStack() {
		if (log.isInfoEnabled()) {
			log.info("Initializing Stack...");
		}
		InputStream is = null;
		try {
			dictionary.parseDictionary(this.getClass().getClassLoader().getResourceAsStream(dictionaryFile));
			log.info("AVP Dictionary successfully parsed.");
			this.stack = new StackImpl();

			is = this.getClass().getClassLoader().getResourceAsStream(configFile);

			Configuration config = new XMLConfiguration(is);
			factory = stack.init(config);
			if (log.isInfoEnabled()) {
				log.info("Stack Configuration successfully loaded.");
			}

			Set<org.jdiameter.api.ApplicationId> appIds = stack.getMetaData().getLocalPeer().getCommonApplications();

			log.info("Diameter Stack  :: Supporting " + appIds.size() + " applications.");
			for (org.jdiameter.api.ApplicationId x : appIds) {
				log.info("Diameter Stack  :: Common :: " + x);
			}
			is.close();
			Network network = stack.unwrap(Network.class);
			network.addNetworkReqListener(this, this.authAppId);
		} catch (Exception e) {
			e.printStackTrace();
			if (this.stack != null) {
				this.stack.destroy();
			}

			if (is != null) {
				try {
					is.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
			return;
		}

		MetaData metaData = stack.getMetaData();
		if (metaData.getStackType() != StackType.TYPE_SERVER || metaData.getMinorVersion() <= 0) {
			stack.destroy();
			if (log.isEnabledFor(org.apache.log4j.Level.ERROR)) {
				log.error("Incorrect driver");
			}
			return;
		}

		try {
			if (log.isInfoEnabled()) {
				log.info("Starting stack");
			}
			stack.start();
			if (log.isInfoEnabled()) {
				log.info("Stack is running.");
			}
		} catch (Exception e) {
			e.printStackTrace();
			stack.destroy();
			return;
		}
		if (log.isInfoEnabled()) {
			log.info("Stack initialization successfully completed.");
		}
	}
	
	private void dumpMessage(Message message, boolean sending) {
		if (log.isInfoEnabled()) {
			log.info((sending?"Sending ":"Received ") + (message.isRequest() ? "Request: " : "Answer: ") + message.getCommandCode() + "\nE2E:"
					+ message.getEndToEndIdentifier() + "\nHBH:" + message.getHopByHopIdentifier() + "\nAppID:" + message.getApplicationId());
			log.info("AVPS["+message.getAvps().size()+"]: \n");
			try {
				printAvps(message.getAvps());
			} catch (AvpDataException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	private void printAvps(AvpSet avpSet) throws AvpDataException {
		printAvpsAux(avpSet, 0);
	}

	/**
	 * Prints the AVPs present in an AvpSet with a specified 'tab' level
	 * 
	 * @param avpSet
	 *            the AvpSet containing the AVPs to be printed
	 * @param level
	 *            an int representing the number of 'tabs' to make a pretty
	 *            print
	 * @throws AvpDataException
	 */
	private void printAvpsAux(AvpSet avpSet, int level) throws AvpDataException {
		String prefix = "                      ".substring(0, level * 2);

		for (Avp avp : avpSet) {
			AvpRepresentation avpRep = AvpDictionary.INSTANCE.getAvp(avp.getCode(), avp.getVendorId());

			if (avpRep != null && avpRep.getType().equals("Grouped")) {
				log.info(prefix + "<avp name=\"" + avpRep.getName() + "\" code=\"" + avp.getCode() + "\" vendor=\"" + avp.getVendorId() + "\">");
				printAvpsAux(avp.getGrouped(), level + 1);
				log.info(prefix + "</avp>");
			} else if (avpRep != null) {
				String value = "";

				if (avpRep.getType().equals("Integer32"))
					value = String.valueOf(avp.getInteger32());
				else if (avpRep.getType().equals("Integer64") || avpRep.getType().equals("Unsigned64"))
					value = String.valueOf(avp.getInteger64());
				else if (avpRep.getType().equals("Unsigned32"))
					value = String.valueOf(avp.getUnsigned32());
				else if (avpRep.getType().equals("Float32"))
					value = String.valueOf(avp.getFloat32());
				else
					value = avp.getOctetString();

				log.info(prefix + "<avp name=\"" + avpRep.getName() + "\" code=\"" + avp.getCode() + "\" vendor=\"" + avp.getVendorId()
						+ "\" value=\"" + value + "\" />");
			}
		}
	}

	
	

	/**
	 * @return
	 */
	private boolean finished() {
		return this.finished;
	}

	public static void main(String[] args) {
		ExampleServer es = new ExampleServer();
		es.initStack();

		while (!es.finished()) {
			try {
				Thread.currentThread().sleep(5000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.jdiameter.api.NetworkReqListener#processRequest(org.jdiameter.api
	 * .Request)
	 */
	@Override
	public Answer processRequest(Request request) {
		dumpMessage(request,false);
		if (request.getCommandCode() != commandCode) {
			log.error("Received bad answer: " + request.getCommandCode());
			return null;
		}
		AvpSet requestAvpSet = request.getAvps();

		Avp exchangeTypeAvp = requestAvpSet.getAvp(exchangeTypeCode, vendorID);
		Avp exchangeDataAvp = requestAvpSet.getAvp(exchangeDataCode, vendorID);
		if (exchangeTypeAvp == null) {
			log.error("Request does not have Exchange-Type");

			Answer answer = createAnswer(request, 5004, EXCHANGE_TYPE_TERMINATING); 
			dumpMessage(answer,true);
			return answer; // set
																			// exchange
																			// type
																			// to
																			// terminating
		}
		if (exchangeDataAvp == null) {
			log.error("Request does not have Exchange-Data");
			Answer answer = createAnswer(request, 5004, EXCHANGE_TYPE_TERMINATING); 
			dumpMessage(answer,true);
			return answer; // set
																			// exchange
																			// type
																			// to
																			// terminating
		}
		// cast back to int(Enumerated is Unsigned32, and API represents it as
		// long so its easier
		// to manipulate
		try {
			switch ((int) exchangeTypeAvp.getUnsigned32()) {
			case EXCHANGE_TYPE_INITIAL:
				// JIC check;
				String data = exchangeDataAvp.getUTF8String();
				this.session = this.factory.getNewSession(request.getSessionId());
				if (data.equals(TO_RECEIVE[toReceiveIndex])) {
					// create session;

					Answer answer = createAnswer(request, 2001, EXCHANGE_TYPE_INITIAL); // set
																								// exchange
																								// type
																								// to
																								// terminating
					toReceiveIndex++;
					dumpMessage(answer,true);
					return answer;
				} else {
					log.error("Received wrong Exchange-Data: " + data);
					Answer answer = request.createAnswer(6000);
				}
				break;
			case EXCHANGE_TYPE_INTERMEDIATE:
				// JIC check;
				data = exchangeDataAvp.getUTF8String();
				if (data.equals(TO_RECEIVE[toReceiveIndex])) {

					Answer answer = createAnswer(request, 2001, EXCHANGE_TYPE_INTERMEDIATE); // set
																								// exchange
																								// type
																								// to
																								// terminating
					toReceiveIndex++;
					dumpMessage(answer,true);
					return answer;
				} else {
					log.error("Received wrong Exchange-Data: " + data);
				}
				break;
			case EXCHANGE_TYPE_TERMINATING:
				data = exchangeDataAvp.getUTF8String();
				if (data.equals(TO_RECEIVE[toReceiveIndex])) {
					// good, we reached end of FSM.
					finished = true;
					// release session and its resources.
					Answer answer = createAnswer(request, 2001, EXCHANGE_TYPE_TERMINATING); // set
																							// exchange
																							// type
																							// to
																							// terminating
					toReceiveIndex++;
					this.session.release();
					finished = true;
					this.session = null;
					dumpMessage(answer,true);
					return answer;

				} else {
					log.error("Received wrong Exchange-Data: " + data);
				}
				break;
			default:
				log.error("Bad value of Exchange-Type avp: " + exchangeTypeAvp.getUnsigned32());
				break;
			}
		} catch (AvpDataException e) {
			// thrown when interpretation of byte[] fails
			e.printStackTrace();
		} catch (InternalException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//error, something bad happened.
		finished = true;
		return null;
	}

	private Answer createAnswer(Request r, int resultCode, int enumType) {
		Answer answer = r.createAnswer(resultCode);
		AvpSet answerAvps = answer.getAvps();
		// code , value , vendor, mandatory,protected,isUnsigned32
		// (Enumerated)
		Avp exchangeType = answerAvps.addAvp(exchangeTypeCode, (long) enumType, vendorID, true, false, true); // value
																												// is
																												// set
																												// on
																												// creation
		// code , value , vendor, mandatory,protected, isOctetString
		Avp exchengeData = answerAvps.addAvp(exchangeDataCode, TO_RECEIVE[toReceiveIndex], vendorID, true, false, false); // value
																															// is
																															// set
																															// on
																															// creation
		
		
		//add origin, its required by duplicate detection
		answerAvps.addAvp(Avp.ORIGIN_HOST, stack.getMetaData().getLocalPeer().getUri().getFQDN(), true, false, true);
		answerAvps.addAvp(Avp.ORIGIN_REALM, stack.getMetaData().getLocalPeer().getRealmName(), true, false, true);
		return answer;
	}
}
